//
// Created by hjie on 23-8-16.
//
#include <cstring>
#include <string>
#include <iostream>
#include <cstdio>
#include <vector>
#include <sstream>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
//#include <libavcodec/qsv.h>
#include <libavutil/time.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#include <libavutil/hwcontext.h>
//#include <libavutil/hwcontext_qsv.h>
}

struct InputFile
{
    std::string filename;
    AVFormatContext * format_ctx;
    int video_index{0};
    int audio_index{0};
    AVCodecContext * video_decoder_ctx;
    AVCodecContext * audio_decoder_ctx;
    AVBufferRef * device_buf;
    AVPacket * m_read_packet;
    AVFrame  * m_decoder_frame;
};

static AVPixelFormat GetFormat(AVCodecContext * codec_tx, const enum AVPixelFormat * pix_fmts)
{
    while (*pix_fmts != AV_PIX_FMT_NONE)
    {
        if (*pix_fmts == AV_PIX_FMT_QSV)
        {
            return AV_PIX_FMT_QSV;
        }
        pix_fmts++;
    }
    printf("no find qsv pix fmt.\n");
    return AV_PIX_FMT_NONE;
}

static int DecoderVideoData(InputFile & input_data, AVPacket * packet)
{
    int result = 0;
    result = avcodec_send_packet(input_data.video_decoder_ctx, input_data.m_read_packet);
    if (result < 0)
    {
        return -1;
    }
    AVFrame * tmp_frame = av_frame_alloc();
    while (result >= 0)
    {
        result = avcodec_receive_frame(input_data.video_decoder_ctx, input_data.m_decoder_frame);
        if (result < 0)
        {
            if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
            {
                result = 0;
            }
            else
            {
                result = -1;
            }
            av_frame_unref(input_data.m_decoder_frame);
            break;
        }
        result = av_hwframe_transfer_data(tmp_frame, input_data.m_decoder_frame, 0);
        if (result < 0)
        {
            av_frame_unref(input_data.m_decoder_frame);
            break;
        }
        std::stringstream ss;
        ss << "frame pts : " << input_data.m_decoder_frame->pts << "\tpkt_dts : " << input_data.m_decoder_frame->pkt_dts;
        printf("%s.\n", ss.str().c_str());

        av_frame_unref(input_data.m_decoder_frame);
        av_frame_unref(tmp_frame);
    }
    return result;
}

int main(int argc, const char * argv[])
{

    if (argc < 2)
    {
        return EXIT_FAILURE;
    }
    InputFile input_data = {};
    input_data.video_index = -1;
    input_data.audio_index = -1;
    input_data.filename = argv[1];
    input_data.format_ctx = nullptr;

    int result = 0;

    result = avformat_open_input(&input_data.format_ctx, input_data.filename.c_str(), NULL, NULL);
    if (result < 0)
    {
        printf("open input file fail.\n");
        return EXIT_FAILURE;
    }
    avformat_find_stream_info(input_data.format_ctx, NULL);

    for (int index = 0; index < input_data.format_ctx->nb_streams; index++)
    {
        AVStream * stream = input_data.format_ctx->streams[index];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            input_data.video_index = index;
        }
        else
        {
            stream->discard = AVDISCARD_ALL;
        }
    }
    result = av_hwdevice_ctx_create(&input_data.device_buf, AV_HWDEVICE_TYPE_QSV, "auto", NULL, 0);
    if (result < 0)
    {
        printf("Cannot open the hardware device.\n");
        return EXIT_FAILURE;
    }
    const AVCodec * decoder = avcodec_find_decoder_by_name("h264_qsv");
    if (!decoder)
    {
        printf("find decoder h264_qsv fail.\n");
        return EXIT_FAILURE;
    }
    input_data.video_decoder_ctx = avcodec_alloc_context3(decoder);
    avcodec_parameters_to_context(input_data.video_decoder_ctx, input_data.format_ctx->streams[input_data.video_index]->codecpar);
    input_data.video_decoder_ctx->codec_id = AV_CODEC_ID_H264;
    input_data.video_decoder_ctx->pkt_timebase = input_data.format_ctx->streams[input_data.video_index]->time_base;
    input_data.video_decoder_ctx->hw_device_ctx = av_buffer_ref(input_data.device_buf);
    input_data.video_decoder_ctx->get_format    = GetFormat;

    if (avcodec_open2(input_data.video_decoder_ctx, decoder, NULL) < 0)
    {
        return -1;
    }
    input_data.m_decoder_frame = av_frame_alloc();
    input_data.m_read_packet = av_packet_alloc();
    AVFrame * frame = av_frame_alloc();
    while (result >= 0)
    {
        result = av_read_frame(input_data.format_ctx, input_data.m_read_packet);
        if (result < 0)
        {
            break;
        }
        if (input_data.m_read_packet->stream_index == input_data.video_index)
        {
            if (DecoderVideoData(input_data, input_data.m_read_packet) < 0)
            {
                break;
            }
        }
        av_packet_unref(input_data.m_read_packet);
    }
    DecoderVideoData(input_data, NULL);
    return EXIT_SUCCESS;
}

